﻿#include "audio/mixer.h"
#include <cstring>

using namespace std;

namespace anip
{
	namespace audio
	{
		Device::Device(const RtAudio::DeviceInfo& d, int i):
			index(i), name(d.name),
			inChannels(d.inputChannels),
			outChannels(d.outputChannels),
			defaultInput(d.isDefaultInput),
			defaultOutput(d.isDefaultOutput)
		{
		}

		Playback::Playback(SoundInstance& s):
			sound(s), _channelVolume(new double[s.channelCount()])
		{
			//s._isPlaying = true;
			_buffer = new double[Mixer::_bufferLen * s.channelCount()];
			setVolume(1);
		}

		Playback::~Playback()
		{
			// can happen in Mixer::finalize()
			/*for (auto s : Mixer::_sounds)
			{
				assert(s != this);
			}*/

			delete[] _buffer;
			delete[] _channelVolume;
			delete& sound;
		}

		void Playback::stop()
		{
			_stop = true;
			if (onStop) onStop(*this);
			Mixer::_checkStop();
		}

		double Playback::getTimePosition() const
		{
			return _pos / (double)Mixer::_sampleRate;
		}

		void Playback::setTimePosition(double t)
		{
			assert(t >= 0);
			_pos = (unsigned int)(t * Mixer::_sampleRate);
			sound.onSetPosition(_pos);
		}

		RtAudio* Mixer::_rt;
		bool Mixer::_open = false;
		unsigned int Mixer::_channels;
		unsigned int Mixer::_bufferLen;
		unsigned int Mixer::_sampleRate;
		vector<Playback*> Mixer::_sounds;
		double Mixer::_globalVolume = 1;

		void Mixer::init()
		{
			_rt = new RtAudio();
		}

		vector<Device> Mixer::devices()
		{
			vector<Device> devs;
			int n = _rt->getDeviceCount();
			for (int i = 0; i < n; i++) {
				auto info = _rt->getDeviceInfo(i);
				Device dev(info, i);
				devs.push_back(dev);
			}
			return devs;
		}

		Device Mixer::defaultInput()
		{
			int i = _rt->getDefaultInputDevice();
			Device dev{_rt->getDeviceInfo(i), i};
			return dev;
		}

		Device Mixer::defaultOutput()
		{
			int i = _rt->getDefaultOutputDevice();
			Device dev{_rt->getDeviceInfo(i), i};
			return dev;
		}

		void Mixer::begin(int sampleRate, unsigned int bufferLength)
		{
			assert(!_open);
			begin(defaultOutput());
		}

		void Mixer::begin(const Device& dev, int sampleRate, unsigned int bufferLength)
		{
			assert(!_open);
			RtAudio::StreamParameters p;
			p.deviceId = dev.index;
			p.firstChannel = 0;
			p.nChannels = dev.outChannels;
			_channels = dev.outChannels;
			_bufferLen = bufferLength;
			_sampleRate = sampleRate;
			_rt->openStream(&p, nullptr, RTAUDIO_FLOAT64, sampleRate, &_bufferLen, _callback);
			_rt->startStream();
			_open = true;
		}

		void Mixer::end()
		{
			assert(_open);
			_rt->stopStream();
			_rt->closeStream();
		}

		void Mixer::setGlobalVolume(double v)
		{
			_globalVolume = v;
		}

		double Mixer::getTime()
		{
			assert(_open);
			return _rt->getStreamTime();
		}

		double Mixer::getLatency()
		{
			assert(_open);
			return _rt->getStreamLatency() / 1000.0;
		}

		int Mixer::_callback(void* outputBuffer, void* inputBuffer, 
			unsigned int nFrames, double streamTime, 
			unsigned int status, void* userData)
		{
			memset(outputBuffer, 0, nFrames * 2 * sizeof(double));
			double* out = (double*)outputBuffer;
			for (Playback* p : _sounds)
			{
				if (p->_pause) continue;
				unsigned int nChannels = p->sound.channelCount();
				if (p->onPlaying) p->onPlaying(*p);
				if (!p->sound.read(p->_buffer, nFrames, p->_pos))
					p->stop();
				else
				{
					p->_pos += nFrames;
					int minChannels = min(_channels, nChannels);
					for (unsigned int i = 0; i < nFrames; i++)
						for (int j = 0; j < minChannels; j++)
							out[i * _channels + j] += 
								p->_buffer[i * nChannels + j] 
								* p->_channelVolume[j]
								* _globalVolume;
				}
			}

			_checkStop();
			return 0;
		}

		void Mixer::_checkStop()
		{
			int size = _sounds.size();
			for (int i = size - 1; i >= 0; i--)
			{
				if (_sounds[i]->_stop)
					_sounds.erase(_sounds.begin() + i);
			}
		}

		Playback* Mixer::addSound(SoundInstance& sound, double volume)
		{
			assert(_open);
			Playback* p = new Playback(sound);
			p->setVolume(volume);
			_sounds.push_back(p);
			return p;
		}

		void Mixer::finalize()
		{
			for (Playback* p : _sounds)
			{
				delete[] p->_buffer;
				delete[] p->_channelVolume;
				delete p;
			}
			if (_rt->isStreamRunning())
				_rt->stopStream();
			if (_rt->isStreamOpen())
				_rt->closeStream();
			delete _rt;
		}
	}
}